package nmf;

import java.util.Random;

import org.ejml.ops.NormOps;
import org.ejml.ops.RandomMatrices;
import org.ejml.simple.SimpleMatrix;

public class NMFAlgorithm2 {
   static double epsilon = .01;
   static int bestRValue = -1;
   
   public static Results getRValue(final SimpleMatrix V) {
      final Results[] results = new Results[3];
      final double M = V.numCols() * V.numRows() / (V.numCols() + V.numRows()) * 3;
      final int[] rValues = { (int) (M / 2), (int) (3 * M / 4), (int) (M / 4) };
      int lowest = 0;
      
      for (int i = 0; i < 3; i++) {
         results[i] = factor(V, rValues[i]);
      }
      
      for (int i = 1; i < 3; i++) {
         if (results[i].error < results[lowest].error) {
            lowest = i;
         }
      }
      bestRValue = rValues[lowest];
      return results[lowest];
   }
   
   public static int getNumIterations(final SimpleMatrix V) {
      //TODO Figure out feasible number of iterations for large matrices
      //return 1000;
      return 10;
   }
   
   public static double FrobeniusCost(final SimpleMatrix V, final SimpleMatrix W, final SimpleMatrix H) {
      return NormOps.fastNormF(V.minus(W.mult(H)).getMatrix());
   }
   
   //TODO: Figure out an acceptable range of error.
   public static double getAcceptableError() {
      return 10;
   }
   
   public static void updateW(final SimpleMatrix V, final SimpleMatrix W, final SimpleMatrix H) {
      for (int i = 0; i < W.numRows(); i++) {
         for (int a = 0; a < W.numCols(); a++) {
            W.set(i, a, W.get(i, a) * V.mult(H.transpose()).get(i, a) / (W.mult(H).mult(H.transpose()).get(i, a) + epsilon));
         }
      }
   }
   
   public static void updateH(final SimpleMatrix V, final SimpleMatrix W, final SimpleMatrix H)
   {
      for (int b = 0; b < H.numRows(); b++) {
         for (int j = 0; j < H.numCols(); j++) {
            H.set(b, j, H.get(b, j) * W.transpose().mult(V).get(b, j) / (W.transpose().mult(W).mult(H).get(b, j) + epsilon));
         }
      }
   }
   
   public static SimpleMatrix factor(final SimpleMatrix V) {
      final Results results = getRValue(V);
      //return results.W.transpose();
      System.out.println(results.error);
      return results.W;
   }
   
   public static void initSimpleMatrix(final SimpleMatrix B) {
      RandomMatrices.setRandom(B.getMatrix(), new Random());
   }
   
   private static void initWH(final SimpleMatrix W, final SimpleMatrix H, final SimpleMatrix V) {
      RandomMatrices.setRandom(W.getMatrix(), new Random());
      RandomMatrices.setRandom(H.getMatrix(), new Random());
   }
   
   public static Results factor(final SimpleMatrix V, int r) {
      int numIters = getNumIterations(V);
      
      // this line i added
      if (r == 0) {
         r = 1;
      }
      final SimpleMatrix W = new SimpleMatrix(new double[V.numRows()][r]), H =
            new SimpleMatrix(new double[r][V.numCols()]);
      /*SimpleMatrix A = new SimpleMatrix(new double[V.numRows()][r]), B =
         new SimpleMatrix(new double[r][V.numCols()]);*/
      
      //int Witers = getNumIterations(W);
      /*RandomMatrices.setRandom(A.getMatrix(), new Random());
      RandomMatrices.setRandom(B.getMatrix(), new Random());*/
      initWH(W, H, V);
      
      /*while (Witers-- > 0) {
         setUpMatrix(A.mult(B), W.mult(B));
      }*/
      
      do {
         updateH(V, W, H);
         updateW(V, W, H);
         System.out.println(numIters);
      } while (--numIters > 0);
      
      //return new Results(W, FrobeniusCost(V, W, H));
      return new Results(H, FrobeniusCost(V, W, H));
   }
   
   public static class Results {
      public Results(final SimpleMatrix W, final double error) {
         this.W = W;
         this.error = error;
      }
      
      public SimpleMatrix W;
      public double error;
   }
   
   public static void setUpMatrix(final SimpleMatrix B, final SimpleMatrix A) {
      initSimpleMatrix(A.mult(B.transpose()));
      
   }
   
   public static void main(final String[] args)
   {
      System.out.println("Hello, World");
   }
   
}